#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAX_LEN 128
#define MAX_SUB_PATH_CNT 10
#define MAX_ARG_CNT 10
#define MAX_PIPELINE_LEN 10

const char arg_tokenizers[] = {' '};
const int arg_tokenizers_len = 1;
const char file_path_tokenizers[] = {'/'};
const int file_path_tokenizers_len = 1;

char cwd[MAX_LEN];
int argc = 0;
char *argv[MAX_ARG_CNT+1], argv_buf[MAX_ARG_CNT][MAX_LEN];
int pipeline[MAX_PIPELINE_LEN][2];

static void init() {
    memset(cwd, 0, sizeof(cwd));
    memset(argv, 0, sizeof(argv));
    if (getcwd(cwd, MAX_LEN) == NULL)
        perror("getcwd");
}

static int contains(const char tokenizers[], const int len, char ch) {
    int i;

    for (i = 0; i < len; ++i) {
        if (ch == tokenizers[i]) {
            return 1;
        }
    }
    return 0;
}

static int tokenize(char *src, const char tokenizers[], const int len, char dst[][MAX_LEN], int *dst_len, const int max_dst_cnt, const char* desc) {
    int i, j, k, n;

    i = 0, j = 0, k = 0, n = strlen(src);
    while (i < n) {
        while (i < n && contains(tokenizers, len, src[i])) ++i;
        if (i < n) {
            j = i + 1;
            while (j < n && !contains(tokenizers, len, src[j])) ++j;
            if (k == max_dst_cnt) {
                fprintf(stderr, "Error: %s tokenize counts reach uplimit\n", desc);
                return -1;
            }
            if (j - i >= MAX_LEN) {
                fprintf(stderr, "Error: token too long\n");
                return -1;
            }
            strncpy(dst[k], src+i, j-i);
            dst[k][j-i] = '\0'; // strncpy add \0 in the end
            k++;
            i = j;
        }
    }

    *dst_len = k;
    return 0;
}

static int get_input() {
    int i, ret;
    char buf[MAX_LEN];
    // char argv_buf[MAX_ARG_CNT][MAX_LEN]; do not assign local variable's pointer 

    memset(buf, 0, sizeof(buf));
    if (fgets(buf, sizeof(buf), stdin) == NULL) {
        perror("fgets");
        return -1;
    }

    /* Remove '\n' at the end of buf */
    buf[strlen(buf)-1] = '\0';

    /* Parse delimiter of any whitespace */
    ret = tokenize(buf, arg_tokenizers, arg_tokenizers_len, argv_buf, &argc, MAX_ARG_CNT, "argument");

    /* Convert buf to pointer */
    for (i = 0; i < argc; ++i) {
        argv[i] = argv_buf[i];
    }

    return ret;
}

static int run_cmd_ch() {
    int i, ret;
    int sub_path_start, sub_path_cnt, sub_cwd_cnt = 0;
    char *path, *cur;
    char target_path[MAX_LEN] = {0}, sub_cwd[MAX_SUB_PATH_CNT][MAX_LEN] = {0}, sub_path[MAX_SUB_PATH_CNT][MAX_LEN] = {0};

    if (argc < 2) {
        printf("Usage: cd [path]\n");
        return -1;
    }

    path = argv[1];
    if (path[0] == '/') { /* Absolute path */
        if (chdir(path) < 0) {
            printf("chdir\n");
            return -1;
        }
    } else { /* Relative path */
        /* Parse current working directory */
        if ((ret = tokenize(cwd, file_path_tokenizers, file_path_tokenizers_len, sub_cwd, &sub_cwd_cnt, MAX_SUB_PATH_CNT, "current working directory")) < 0) 
            return ret;

        /* Parse path */
        if ((ret = tokenize(path, file_path_tokenizers, file_path_tokenizers_len, sub_path, &sub_path_cnt, MAX_SUB_PATH_CNT, "path")) < 0) 
            return ret;

        /* Count backward */
        sub_path_start = 0;
        while (sub_path_start < sub_path_cnt) {
            if (strcmp(sub_path[sub_path_start], ".") == 0) {
                sub_path_start++;
            } else if (strcmp(sub_path[sub_path_start], "..") == 0) {
                sub_cwd_cnt--;
                sub_path_start++;
            } else {
                break;
            }
        }

        /* Fill target path */
        cur = target_path;
        for (i = 0; i < sub_cwd_cnt; ++i) {
            memcpy(cur, "/", 1);
            cur += 1;
            memcpy(cur, sub_cwd[i], strlen(sub_cwd[i]));
            cur += strlen(sub_cwd[i]);
        }
        for (i = sub_path_start; i < sub_path_cnt; ++i) {
            memcpy(cur, "/", 1);
            cur += 1;
            memcpy(cur, sub_path[i], strlen(sub_path[i])); // char[] strlen; char* strlen, no sizeof
            cur += strlen(sub_path[i]);
        }

        if (chdir(target_path) < 0) {
            printf("chdir\n");
            return -1;
        }
    } 

    return 0;
}

static int run_cmd_redirect(int redirect_type, int redirect_symbol_idx, int redirect_file_flag, int redirect_file_mode) {
    int fd, status_val;
    pid_t pid = -1;
    char *redirect_file_path;

    if (redirect_symbol_idx+1 >= argc) {
        fprintf(stderr, "Error: redirect\n");
        return -1;
    }

    redirect_file_path = argv[redirect_symbol_idx+1];

    /* clear arg after redirect token */
    argv[redirect_symbol_idx] = NULL;

    pid = fork();

    if (pid < 0) {
        perror("fork");
        return -1;
    }

    if (pid == 0) { /* Child process */
        if ((fd = open(redirect_file_path, redirect_file_flag, redirect_file_mode)) < 0) {
            perror("open");
            exit(-1); // child process use exit() instead of return, return just drop the top frame, execute next frame's code
            // exit: flush buffer/close fds/run register func `atexit`/send exit signal to parent process
        }
        if (dup2(fd, redirect_type) < 0) {
            perror("dup2");
            close(fd);
            exit(-1);
        }
        close(fd);
        execvp(argv[0], argv); // won't return if success
        perror("exec");
        exit(-1);
    } else { /* Parent process */
        waitpid(pid, &status_val, 0);
        if (WIFEXITED(status_val)) {
            return status_val;
        } else {
            fprintf(stderr, "Error: child process terminated abnormally\n");
            return -1;
        }
    }

    return 0;
}

// designe fatal: can we use pipe's read wait to run multiple command concurrently?
// static int run_cmd_pipe(int s) {
//     int i, argv_start, cur, status_val;
//     pid_t pid = -1;

//     /* command1 | command 2 | command 3 ... */
//     argv_start = 0, cur = 0;
//     for (i = s; i < argc; ++i) {
//         if (strcmp(argv[i], "|") == 0) {
//             argv[i] = NULL;

//             /* Create pipe for cur command */
//             if (pipe(pipeline[cur]) < 0) {
//                 perror("pipe");
//                 goto exit;
//             }

//             pid = fork();
//             if (pid < 0) {
//                 perror("fork");
//                 return -1;
//             }

//             if (pid == 0) {
//                 /* redirect cur command's output to cur pipe's input */
//                 if (dup2(pipeline[cur][0], stdout) < 0) {
//                     perror("dup2");
//                     exit(1);
//                 }
//                 /* close cur pipe's output */
//                 close(pipeline[cur][1]);

//                 /* redirect cur command's input to cur-1 pipe (except the first command) */
//                 if (cur > 0) {
//                     if (dup2(pipeline[cur-1][1], stdin) < 0) {
//                         perror("dup2");
//                         exit(1);
//                     }
//                     /* close cur pipe's output */
//                     close(pipeline[cur-1][0]);
//                 }
//                 execvp(argv[argv_start], &argv[argv_start]);
//                 perror("exec");
//                 exit(-1);
//             } else {
//                 waitpid(pid, &status_val, 0);
//                 if (WIFEXITED(status_val)) {
//                 } else {
//                     fprintf(stderr, "Error: child process terminated abnormally\n");
//                 }
//                 argv_start = i + 1;
//                 cur += 1;
//             }
//         }
//     }

//     return 0;
// }

/* command1 | command 2 | command 3 ... */
static int run_cmd_pipe(int s) {
    int i, j, argv_start, cur;
    pid_t pid = -1;
    
    /* Create pipe for cur command */
    for (i = 0; i < MAX_ARG_CNT; ++i) {
        if (pipe(pipeline[i]) < 0) {
            perror("pipe");
            return -1;
        }
    }
    
    /* Last command */ 
    // put before to use the same code
    argv_start = 0, cur = 0;
    for (i = s; i <= argc; ++i) {
        if (i == argc || strcmp(argv[i], "|") == 0) {
            argv[i] = NULL;

            pid = fork();
            if (pid < 0) {
                perror("fork");
                return -1;
            }

            if (pid == 0) {
                /* Redirect command input to pre pipe output (except the first command) */
                if (cur > 0) {
                    dup2(pipeline[cur-1][1], 0);
                }

                /* Redirect command output to cur pipe input (except the last command) */
                if (i != argc) {
                    dup2(pipeline[cur][0], 1);
                }

                /* Close pipe fd for child process */
                for (j = 0; j < MAX_PIPELINE_LEN; ++j) {
                    close(pipeline[j][0]);
                    close(pipeline[j][1]);
                }
                execvp(argv[argv_start], &argv[argv_start]);
                perror("exec");
                exit(-1);
            }

            cur += 1;
            argv_start = i + 1;
        }
    }

    /* Close all pipe fd for parent process*/
    for (i = 0; i < MAX_PIPELINE_LEN; ++i) {
        close(pipeline[i][0]);
        close(pipeline[i][1]);
    }

    /* Wait all sub process */
    for (i = 0; i < cur; ++i) {
        wait(NULL);
    }

    return 0;
}

static int run_cmd() {
    int status_val;
    pid_t pid = -1;

    argv[argc] = NULL;

    pid = fork();

    if (pid < 0) {
        perror("fork");
        return -1;
    }

    if (pid == 0) { /* Child process */
        execvp(argv[0], argv); // won't return if success
        perror("exec");
        exit(-1);
    } else { /* Parent process */
        waitpid(pid, &status_val, 0);
        if (WIFEXITED(status_val)) {
            return status_val;
        } else {
            fprintf(stderr, "Error: child process terminated abnormally\n");
            return -1;
        }
    }

    return 0;
}

static int parse_execute() {
    int i;
    int redirect_file_mode = 0777;

    if (argc < 1) {
        return 0;
    }

    /* Special command do not need sub process to run */
    if (strcmp(argv[0], "exit") == 0) { /* exit */
        exit(0);
    } else if (strcmp(argv[0], "pwd") == 0) { /* pwd */
        printf("%s\n", cwd);
        return 0;
    } else if (strcmp(argv[0], "cd") == 0) { /* cd */
        return run_cmd_ch();
    }
    
    /* Redirect command*/
    for (i = 0; i < argc; ++i) {
        if (strcmp(argv[i], "<") == 0) {
            return run_cmd_redirect(0, i, O_RDONLY, redirect_file_mode);
        } else if (strcmp(argv[i], ">") == 0) {
            return run_cmd_redirect(1, i, O_WRONLY | O_CREAT, redirect_file_mode);
        } else if (strcmp(argv[i], ">>") == 0) {
            return run_cmd_redirect(1, i, O_WRONLY | O_CREAT | O_APPEND, redirect_file_mode);
        } else if (strcmp(argv[i], "|") == 0) {
            return run_cmd_pipe(i);
        }
    }

    /* Basic command */
    return run_cmd();
}

int main() {
    while (1) {
        init();
        printf("%s:$ ", cwd);
        get_input();
        parse_execute();
    }

    return 0;
}